package com.hong.qrpc.spring.springboot.starter.postprocessor;

import com.hong.qrpc.RemoteInvoke;
import com.hong.qrpc.annotation.QRpcClient;
import com.hong.qrpc.spring.springboot.starter.ClientStarter;
import com.hong.qrpc.spring.springboot.starter.ServerStarter;
import com.hong.qrpc.utils.StringUtils;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

/**
 * @Author HQT
 * @Date 2022/7/31 11:41
 */
@Data
@Slf4j
@Component
public class QrpcPostProcessor implements BeanFactoryPostProcessor, CommandLineRunner {
    /**
     * 用于放置需要修改属性的代理对象
     */
    List<RemoteInvoke> remoteInvokes = new ArrayList<>();
    /**
     * 用于协助服务端的接口实例对象获取
     */
    List<Class> classes = new ArrayList<>();
    /**
     * 用于快速判断一个类是否在成员变量 classes中
     */
    HashSet<Class> classesSet = new HashSet<>();
    ConfigurableListableBeanFactory beanFactory;
    Map<String, Object> clientAttrs;
    Map<String, Object> serverAttrs;

    /**
     * 在此处进行bean的初始化注入，并且保存需要被重新修改属性的对象
     *
     * @param configurableListableBeanFactory bean工厂
     * @throws BeansException
     */
    @SneakyThrows
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        beanFactory = configurableListableBeanFactory;
        clientAttrs = QrpcImportBeanDefinitionRegistrar.getClientAttrs();
        serverAttrs = QrpcImportBeanDefinitionRegistrar.getServerAttrs();

        //进行注册
        //如果是服务端的话需要记录下rpc接口的class以便在启动完成后进行服务实例的注册，如果是客户端的话就要记录下生成代理对象的对象，
        //通过修改代理对象内部属性的方式完成代理
        if (serverAttrs != null) {
            if (serverAttrs.get("basePackages") != null) {
                String[] packageUrls = (String[]) serverAttrs.get("basePackages");
                register(packageUrls, false);
            }
        }
        //将即将作为服务提供者的类的class添加到bean工厂中，以免在工厂中注入代理对象
        for (Class clazz : classes) {
            classesSet.add(clazz);
        }
        if (clientAttrs != null) {
            if (clientAttrs.get("basePackages") != null) {
                String[] packageUrls = (String[]) clientAttrs.get("basePackages");
                register(packageUrls, true);
            }
        }
    }

    /**
     * 此方法在springboot初始化完毕之后会被调用，该方法用于注册客户/服务端实例，并且将客户/服务顿交给容器管理
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(String... args) throws Exception {
        QrpcPostProcessor thisObj = this;
        if (serverAttrs != null) {
            beanFactory.getBean(ServerStarter.class).serverStart(thisObj);
        }
        if (clientAttrs != null) {
            beanFactory.getBean(ClientStarter.class).clientStart(thisObj);
        }
    }

    private void register(String[] packageUrls, boolean isClient) throws IOException {
        for (String packageUrl : packageUrls) {
            packageUrl = StringUtils.replaceAll('.', '/', packageUrl);
            packageUrl = StringUtils.replaceAll('\\', '/', packageUrl);
            registerFromPackage(packageUrl, isClient);
        }
    }

    /**
     * 获取用户需要扫描的路径下的所有class，并加载有QRpcClient注解的类
     *
     * @param path
     * @param isClient
     * @throws IOException
     */
    private void registerFromPackage(String path, boolean isClient) throws IOException {
        path = getPatternPath(path);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resolver.getResources(path);
        for (Resource resource : resources) {
            String classPath = resource.getURL().getPath();
            int idx = classPath.indexOf("/classes/");
            if (idx == -1) {
                continue;
            }
            String className = StringUtils.replaceAll('/', '.',
                    classPath.substring(idx + "/classes/".length(), classPath.length() - ".class".length()));
            try {
                Class clazz = Class.forName(className);
                QRpcClient annotation = null;
                if ((annotation = (QRpcClient) clazz.getAnnotation(QRpcClient.class)) != null) {
                    if (!clazz.isInterface()) {
                        log.error("注解@QRpcClient不应该用于非接口上");
                        continue;
                    }
                    if (isClient) {
                        //如果这个类是服务提供者的话就不用注册代理对象了
                        if (!classesSet.contains(clazz)) {
                            clientRegister(clazz);
                        }
                    } else {
                        serverRegister(clazz);
                    }
                    log.debug("loaded class:" + clazz);
                }
            } catch (Exception e) {
                //加载到某些第三方的类可能会出现异常，此处不作处理
                log.debug("loaded class:" + className + "  fail");
            }
        }
    }

    /**
     * 将用户的输入路径处理成正确的模式串
     *
     * @param path
     * @return
     */
    private String getPatternPath(String path) {
        path = StringUtils.replaceAll('.', '/', path);
        if (path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
        }
        StringBuilder sb = new StringBuilder().append("classpath*:").append(path);
        if (path.endsWith("/**")) {
            sb.append("/*.class");
        } else if (path.endsWith("/*")) {
            sb.append("/*.class");
        } else {
            sb.append("/**/*.class");
        }
        return sb.toString();
    }

//    /**
//     * 递归地去寻找需要加载的服务实例对象
//     *
//     * @param curFile 方便获取路径和进行递归
//     */
//    private void registerFromPackage(File curFile, Deque<String> subUrls, boolean isClient) {
//        if (curFile.isFile()) {
//            if (curFile.canRead()) {
//                String curPath = curFile.getPath();
//                if (curPath.endsWith(".class")) {
//                    try {
//                        Class clazz = Class.forName(StringUtils.getClassName(subUrls));
//                        QRpcClient annotation = null;
//                        if ((annotation = (QRpcClient) clazz.getAnnotation(QRpcClient.class)) != null) {
//                            if (isClient) {
//                                clientRegister(clazz);
//                            } else {
//                                serverRegister(clazz);
//                            }
//                        }
//                    } catch (ClassNotFoundException e) {
//                        e.printStackTrace();
//                    }
//                }
//            }
//        } else {
//            for (String subUrl : curFile.list()) {
//                subUrls.addLast(subUrl);
//                registerFromPackage(new File(curFile, subUrl), subUrls, isClient);
//                subUrls.removeLast();
//            }
//        }
//    }

    private void clientRegister(Class clazz) {
        //如果bean工厂已经存在了这个bean就说明已经注入了该接口的服务对象，对于这个接口类来说本机器是服务端而不是客户端
        try {
            beanFactory.getBean(clazz);
            return;
        } catch (Exception e) {

        }
        RemoteInvoke remoteInvoke = new RemoteInvoke(clazz);
        Object proxyInstance = Proxy.newProxyInstance(
                remoteInvoke.getClass().getClassLoader(),
                new Class[]{clazz},
                remoteInvoke
        );
        beanFactory.registerSingleton(StringUtils.getSimpleName(clazz), proxyInstance);
        remoteInvokes.add(remoteInvoke);
    }

    private void serverRegister(Class clazz) {
        classes.add(clazz);
    }
}
